contents
객체지향 프로그래밍(OOP) vs 함수형 프로그래밍(FP): 상세 설명과 코드 예제
**OOP(객체지향 프로그래밍)**과 **FP(함수형 프로그래밍)**의 차이를 이해하는 것은 현대 소프트웨어 개발에서 매우 중요합니다. 아래에서는 두 패러다임의 개념, 핵심 원칙, 차이점, 그리고 실전 코드 예제를 포함한 심층 비교를 제공합니다.
1. 객체지향 프로그래밍 (OOP)
OOP란?
OOP는 데이터를 **객체(Objects)**의 형태로 모델링하는 프로그래밍 패러다임입니다. 객체는 **데이터(필드)**와 **행동(메서드)**를 함께 묶은 단위로, 현실 세계의 개체와 관계를 표현하는 데 초점이 있습니다.
OOP의 4대 핵심 원칙 (4대 특성)
- 캡슐화(Encapsulation): 데이터와 메서드를 묶고 외부로부터 내부를 숨기기
- 추상화(Abstraction): 필수적인 데이터만 외부에 노출
- 상속(Inheritance): 클래스 간의 속성과 행동 공유
- 다형성(Polymorphism): 동일한 인터페이스로 다양한 구현 수행 가능
어떤 경우에 사용?
- 현실 세계 모델링 (은행 업무, 주문 처리, 재고 관리 등)
- 대형 엔터프라이즈 시스템
- UI 컴포넌트 기반 프레임워크
- 데이터와 기능을 함께 관리해야 하는 시스템
Java 기반 예제
abstract class Animal {
String name;
public Animal(String name) { this.name = name; }
public abstract void speak();
}
class Dog extends Animal {
public Dog(String name) { super(name); }
public void speak() { System.out.println(name + " says: 멍멍!"); }
}
class Cat extends Animal {
public Cat(String name) { super(name); }
public void speak() { System.out.println(name + " says: 야옹!"); }
}
public class Zoo {
public static void main(String[] args) {
Animal a1 = new Dog("바비");
Animal a2 = new Cat("키티");
a1.speak(); // 다형성: Dog.speak() 호출
a2.speak(); // 다형성: Cat.speak() 호출
}
}
2. 함수형 프로그래밍 (Functional Programming, FP)
FP란?
FP는 수학적 함수의 평가를 기반으로 하여 실행되는 패러다임으로, **불변성(immutability)**과 **순수 함수(pure function)**를 강조합니다.
FP의 핵심 개념
- 순수 함수: 같은 입력 → 같은 출력, 사이드 이펙트 없음
- 불변 데이터: 상태가 변하지 않음 → 새 객체를 반환
- 고차 함수(Higher-order function): 함수가 다른 함수를 인자나 결과로 사용할 수 있음
- 1급 함수(First-class function): 함수를 변수처럼 다룸
- 선언형 스타일: “무엇을” 할 것인지 기술하며, “어떻게”는 추상화
어떤 경우에 사용?
- 대량 데이터 처리 및 분석
- 병렬/비동기 작업 (상태 공유 최소화)
- 순수 데이터 흐름 처리 (스트리밍, 리액티브)
- 예측 가능성 높은 코드, 테스트 용이성 강화
Kotlin 예제
fun square(x: Int): Int = x * x
fun applyToList(lst: List<Int>, f: (Int) -> Int): List<Int> = lst.map(f)
val nums = listOf(1, 2, 3, 4)
val squared = applyToList(nums, ::square)
println(squared) // [1, 4, 9, 16]
val evensSum = nums.filter { it % 2 == 0 }.sum()
println(evensSum) // 6 (2+4)
Java Streams 예제
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> squares = nums.stream()
.map(x -> x * x)
.toList(); // [1, 4, 9, 16]
int sumEven = nums.stream()
.filter(x -> x % 2 == 0)
.mapToInt(x -> x)
.sum(); // 6
3. OOP vs FP 차이점 정리
| 항목 | 객체지향 프로그래밍(OOP) | 함수형 프로그래밍(FP) |
|---|---|---|
| 주요 구성 요소 | 클래스, 객체 | 함수 |
| 데이터 처리 방식 | 변경 가능한 상태 (mutable) | 변경 불가 상태 (immutable) |
| 코드 구조 | 데이터 + 함수 결합 | 순수 함수, 상태 없음 |
| 상태 방식 | 객체 내부에 상태 저장 | 상태 없이 함수 체인으로 흐름 구성 |
| 사이드 이펙트 | 허용, 종종 기대됨 | 피해야 함 (예: 전역변수, 로그 출력 등 없음) |
| 추상화 방식 | 상속, 객체 합성 | 함수 합성, 고차 함수 |
| 테스트 난이도 | 내부 상태와 사이드 이펙트로 인해 테스트 어려움 | 순수 함수 덕에 테스트 용이 |
| 주 사용 언어 | Java, C++, C#, Python 등 | Haskell, Kotlin, JavaScript(함수형 스타일), Scala 등 |
4. 실전 예시 비교: 짝수만 제곱한 리스트 만들기
OOP 스타일 (Java)
public class ListProcessor {
public List<Integer> getEvenSquares(List<Integer> numbers) {
List<Integer> result = new ArrayList<>();
for (Integer n : numbers) {
if (n % 2 == 0) {
result.add(n * n);
}
}
return result;
}
}
FP 스타일 (Kotlin)
val numbers = listOf(1,2,3,4,5,6)
val evenSquares = numbers.filter { it % 2 == 0 }.map { it * it }
println(evenSquares) // [4, 16, 36]
5. 함께 사용할 수 있을까?
- Yes! 대부분의 현대 언어는 둘 다 지원합니다.
- Java 8+: 람다, 스트림, 함수형 인터페이스
- Kotlin: FP 친화적인 기능들과 완전한 OOP 구성 가능
- 실무에서는 보통 OOP로 전체 아키텍처, FP로 데이터 처리/비즈니스 로직을 작성하는 경우가 많습니다.
6. 요약 테이블
| 항목 | OOP 예시 | FP 예시 |
|---|---|---|
| 캡슐화(Encapsulation) | user.getName() |
불필요 |
| 불변성(immutability) | 상태 변경 허용 | 변경하지 않고 새 객체 반환 |
| 상속(Inheritance) | class Dog extends Animal |
상속 대신 함수 합성 |
| 함수 전달/반환 | 인터페이스/전략 패턴을 통해 전달 | 함수를 직접 인자나 결과로 사용 가능 |
| 코드 스타일 | 클래스 기반, 상태 집중 | 함수 기반, 선언형 스타일 |
✅ 결론
- OOP: 실생활 객체 모델링, 상태 유지가 필요한 앱(예: ERP, 웹 서버)에 적합
- FP: 테스트 용이, 동시성, 불변성 중심의 계산 중심 로직에 적합
- 모던 개발: 대부분 혼합해서 사용함! 상황에 맞게 OOP ↔ FP 선택
Java Streams 기반 함수형 프로그래밍(FP) vs Spring 기반 객체지향 프로그래밍(OOP) 고급 예제 및 실무 활용
아래는 동일한 비즈니스 시나리오를 Java Streams를 활용한 함수형 스타일(FP) 과 **Spring 서비스를 활용한 객체지향 스타일(OOP)**로 각각 구현한 고급 예제를 통해, 두 패러다임이 어떻게 현대 엔터프라이즈 개발에서 함께 사용될 수 있는지를 보여줍니다.
1. 함수형 프로그래밍 예시 (Java Streams 기반)
시나리오: 🛒 전자상거래 시스템에서의 주문 요약 처리
목표:
- 상태가 PAID인 주문만 필터링
- 고객별로 그룹핑
- 고객별 총 결제 금액 산출
- 총 결제 금액 기준 내림차순 정렬
- 요약 리포트 출력
Java Streams 기반 구현
public class Order {
enum Status { PAID, PENDING, CANCELLED }
private final String customerId;
private final double total;
private final Status status;
// 생성자 및 getter 생략
}
List<Order> orders = ...; // 예: DB에서 불러온 주문 리스트
Map<String, Double> customerTotals = orders.stream()
.filter(o -> o.getStatus() == Order.Status.PAID)
.collect(Collectors.groupingBy(
Order::getCustomerId,
Collectors.summingDouble(Order::getTotal)
));
List<Map.Entry<String, Double>> summary = customerTotals.entrySet().stream()
.sorted(Map.Entry.<String, Double>comparingByValue().reversed())
.collect(Collectors.toList());
summary.forEach(entry ->
System.out.println("고객: " + entry.getKey() + " | 총액: " + entry.getValue())
);
☑️ 특징
- 불변성 및 선언형 스타일
- 반복문 없이 메서드 체이닝
- filter → grouping → mapping → reducing 전형적 흐름
2. Spring OOP 스타일 예제: 주문 생성 및 결제 처리 프로세스
시나리오: 🛠 주문 생성 → 재고 확인 → 결제 처리 → 알림 발송
전통적 Spring 기반 객체지향 설계
@Service
public class OrderService {
private final InventoryService inventory;
private final PaymentService payment;
private final NotificationService notification;
@Autowired
public OrderService(InventoryService inventory, PaymentService payment, NotificationService notification) {
this.inventory = inventory;
this.payment = payment;
this.notification = notification;
}
public void processOrder(Order order) {
if (!inventory.reserve(order)) throw new OutOfStockException();
if (!payment.charge(order)) throw new PaymentFailedException();
notification.orderConfirmed(order);
}
}
각 컴포넌트는 단일 책임 원칙(SRP)을 준수하며 독립적으로 교체/테스트 가능합니다.
3. FP vs OOP 비교: "최근 한 달 내 결제 완료 고객 중 상위 3명에게 쿠폰 발송"
A. Java Streams 로직 (FP 방식)
List<Order> orders = ...;
LocalDate aMonthAgo = LocalDate.now().minusMonths(1);
Map<String, Double> totals = orders.stream()
.filter(o -> o.getStatus() == Order.Status.PAID && o.getDate().isAfter(aMonthAgo))
.collect(Collectors.groupingBy(Order::getCustomerId,
Collectors.summingDouble(Order::getTotal)));
List<String> top3 = totals.entrySet().stream()
.sorted(Map.Entry.<String, Double>comparingByValue().reversed())
.limit(3)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
B. Spring 서비스로 통합 (OOP 방식)
@Service
public class PromotionService {
private final OrderRepository orderRepo;
private final EmailService emailService;
@Autowired
public PromotionService(OrderRepository orderRepo, EmailService emailService) {
this.orderRepo = orderRepo;
this.emailService = emailService;
}
public void sendTopCustomerCoupons() {
List<Order> orders = orderRepo.findPaidInLastMonth();
Map<String, Double> totals = aggregateTotals(orders);
List<String> top3 = getTopN(totals, 3);
for (String customerId : top3) {
emailService.sendCoupon(customerId, "VIP 쿠폰 코드입니다!");
}
}
private Map<String, Double> aggregateTotals(List<Order> orders) { /* FP 코드 재사용 */ }
private List<String> getTopN(Map<String, Double> map, int n) { /* FP 코드 재사용 */ }
}
→ 핵심 데이터 처리 로직은 FP로, 시스템 조율 및 흐름 관리는 OOP로 처리
4. 추가 도메인별 예제
FP: 로그 분석, 데이터 파이프라인, ETL
List<String> logs = ...;
long errorCount = logs.stream()
.filter(line -> line.contains("ERROR"))
.count();
Set<String> users = logs.stream()
.map(this::extractUserId)
.collect(Collectors.toSet());
OOP: 할인 정책 전략 구조, 마이크로서비스 조합
public interface DiscountStrategy {
boolean isApplicable(Order order);
BigDecimal calculateDiscount(Order order);
}
@Component
public class HolidayDiscount implements DiscountStrategy { ... }
@Service
public class DiscountService {
@Autowired List<DiscountStrategy> strategies;
public BigDecimal getBestDiscount(Order order) {
return strategies.stream()
.filter(s -> s.isApplicable(order))
.map(s -> s.calculateDiscount(order))
.max(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
}
}
→ 각 전략은 OOP로 구성되고, 내부 연산은 FP 스트림으로 처리
5. 요약 비교표
| 항목 | 함수형(FP, Java Streams) | 객체지향(Spring 기반 OOP) |
|---|---|---|
| 대표 장점 | 선언적, 병렬/스트림 연산에 이상적 | 의존성 주입과 캡슐화, 구조적 표현 |
| 좋은 용도 | 필터링, 그룹핑, 집계, 데이터 파이프라인 | 도메인 서비스, 트랜잭션, 워크플로우 |
| 적용 위치 | 메서드 내부 로직, DAO/Repository 등 | 서비스 계층, 애플리케이션 조합/조율 |
| 테스트 용이성 | 매우 높음 (순수 함수 기반) | 모킹/DI로 우수한 테스트 구조 |
✅ 결론
-
💡 Java 8+와 Spring 환경에서는 FP와 OOP를 결합하여 사용하는 것이 최선의 설계입니다.
- 데이터 처리 단계: → FP (Streams, 람다 등)
- 서비스/정책 구조: → OOP (DI, 전략 패턴, 서비스 계층 개념)
-
실제 애플리케이션에서는 전처리, 필터링, 계산 등에는 함수형 스타일을 사용하고, 전체적인 비즈니스 흐름 구성은 OOP 기반으로 설계하는 경우가 많습니다.
references